{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3-2BcwIO1r4_"
      },
      "source": [
        "# Simu-Learn - Autonomous Multi-Agent Learning System\n",
        "\n",
        "## Welcome to the autonomous agentic simulation for a virtual learner\n",
        "\n",
        "```\n",
        "Main: Orchestrator → CurriculumDesigner → (for each topic: Tutor → QuizWorkflow)\n",
        "QuizWorkflow: QuizMaster → Learner → Reviewer (repeated N times)\n",
        "```"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "jT3I8L8TMSIK"
      },
      "outputs": [],
      "source": [
        "%pip install -qU ipywidgets"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "metadata": {
        "id": "fI-UMmlKzxh1"
      },
      "outputs": [],
      "source": [
        "import os\n",
        "import random\n",
        "\n",
        "from openai import OpenAI"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NTExiqy425mc"
      },
      "source": [
        "## Utilities"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {
        "id": "CPBxuVQc29N-"
      },
      "outputs": [],
      "source": [
        "def _get_secret_from_environment(name: str) -> str:\n",
        "  secret = os.environ.get(name, '').strip()\n",
        "\n",
        "  if secret:\n",
        "      print(f'✅ Environment Variable: Found {name}')\n",
        "      return secret\n",
        "\n",
        "  print(f'❌ Environment Variable : {name} is not set')\n",
        "\n",
        "  try:\n",
        "    from google.colab import userdata\n",
        "\n",
        "    secret = userdata.get(name)\n",
        "    print(f'✅ Google Colab: Found {name}')\n",
        "  except Exception as e:\n",
        "    print(f'❌ Google Colab: {e}')\n",
        "\n",
        "  return secret.strip()\n",
        "\n",
        "\n",
        "def _get_secret_from_user(name: str) -> str:\n",
        "  from getpass import getpass\n",
        "\n",
        "  return getpass(f'Enter the secret value for {name}: ')\n",
        "\n",
        "def get_secret(name: str) -> str:\n",
        "  secret = _get_secret_from_environment(name)\n",
        "  if not secret:\n",
        "    secret = _get_secret_from_user(name)\n",
        "\n",
        "  return secret"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0xymrLp5DTbU"
      },
      "source": [
        "### Setup LLM Provider (Ollama)\n",
        "\n",
        "🔑 Go to https://ollama.com/ to register for a **FREE** api key, if you need one"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {
        "id": "PPclhwiFA9qu"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "✅ Environment Variable: Found OLLAMA_API_KEY\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "['glm-4.6',\n",
              " 'kimi-k2:1t',\n",
              " 'qwen3-coder:480b',\n",
              " 'deepseek-v3.1:671b',\n",
              " 'gpt-oss:120b',\n",
              " 'gpt-oss:20b',\n",
              " 'qwen3-vl:235b-instruct',\n",
              " 'qwen3-vl:235b',\n",
              " 'minimax-m2']"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "MODEL = 'gpt-oss:20b-cloud'\n",
        "\n",
        "api_key = get_secret('OLLAMA_API_KEY')\n",
        "client = OpenAI(api_key=api_key, base_url='https://ollama.com/v1')\n",
        "\n",
        "\n",
        "[model.id for model in client.models.list()]"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BBM4VbQ0JMQs"
      },
      "source": [
        "## Agents"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "prAvT0E2DSRw"
      },
      "outputs": [],
      "source": [
        "import time\n",
        "from typing import Any\n",
        "from dataclasses import dataclass, field\n",
        "from datetime import datetime, timedelta\n",
        "\n",
        "\n",
        "@dataclass\n",
        "class State:\n",
        "    educational_level: str\n",
        "    subject: str\n",
        "    topics: list[str]\n",
        "    topic: str\n",
        "    mastery: dict[str, float]\n",
        "    session_start: datetime\n",
        "    history: list[Any]\n",
        "    quiz: dict[str, str]\n",
        "    learner_answer: str = field(default='')\n",
        "    num_questions_per_topic: int = field(default=2)\n",
        "    run_duration_minutes: int = field(default=5)\n",
        "\n",
        "\n",
        "@dataclass\n",
        "class AgentResponse:\n",
        "    agent: str\n",
        "    message: str\n",
        "    current_state: State\n",
        "    metadata: dict = field(default_factory=dict)\n",
        "\n",
        "\n",
        "def add_message(role, text):\n",
        "    timestamp = datetime.now().strftime('%H:%M:%S')\n",
        "    print(f'\\n[{timestamp}] {role}: {text}\\n')\n",
        "    time.sleep(1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Q5LI7wJ--CaF"
      },
      "source": [
        "### Agents Definitions"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mieFieVQOJmO"
      },
      "outputs": [],
      "source": [
        "\n",
        "from abc import ABC, abstractmethod\n",
        "from typing import override\n",
        "import json\n",
        "import copy\n",
        "\n",
        "\n",
        "class Agent(ABC):\n",
        "    @abstractmethod\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        ...\n",
        "\n",
        "    def copy_state(self, state: State) -> State:\n",
        "        return copy.deepcopy(state)\n",
        "\n",
        "\n",
        "class CurriculumDesigner(Agent):\n",
        "\n",
        "    @override\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        prompt = f'''\n",
        "        You are an experienced educationist in {state.subject}, tasked\n",
        "        to generate 5 topics for a learner at the {state.educational_level} level as a JSON output.\n",
        "\n",
        "        strictly produce **ONLY JSON output** with the list of topics:\n",
        "        Example:\n",
        "        [\"topic 1\", \"topic 2\"]\n",
        "        '''\n",
        "\n",
        "        resp = client.chat.completions.create(\n",
        "            model=MODEL,\n",
        "            messages=[{'role': 'user', 'content': prompt}],\n",
        "            response_format={\"type\": \"json_object\"},\n",
        "        )\n",
        "        msg = resp.choices[0].message.content\n",
        "        topics = json.loads(msg)\n",
        "\n",
        "        current_state = self.copy_state(state)\n",
        "        current_state.topics = topics\n",
        "\n",
        "        return AgentResponse(\n",
        "            agent='📚 CurriculumDesigner',\n",
        "            message=f'{len(topics)} topics. {str(topics)}',\n",
        "            current_state=current_state,\n",
        "        )\n",
        "\n",
        "\n",
        "class Tutor(Agent):\n",
        "\n",
        "    @override\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        prompt = f'''\n",
        "        You are a tutor in {state.subject}.\n",
        "        Explain {state.topic} a learner at the {state.educational_level} level.\n",
        "        in not more than 2 short paragraphs\n",
        "        '''\n",
        "\n",
        "        resp = client.chat.completions.create(\n",
        "            model=MODEL,\n",
        "            messages=[{'role': 'user', 'content': prompt}],\n",
        "        )\n",
        "        msg = resp.choices[0].message.content\n",
        "\n",
        "        current_state = self.copy_state(state)\n",
        "\n",
        "        return AgentResponse(\n",
        "            agent='📘 Tutor',\n",
        "            message=msg,\n",
        "            current_state=current_state,\n",
        "        )\n",
        "\n",
        "\n",
        "class QuizMaster(Agent):\n",
        "\n",
        "    @override\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        prompt = f'''\n",
        "        You are a tutor in {state.subject} for a learner at the {state.educational_level} level.\n",
        "\n",
        "        Give 1 question on {state.topic} and it's corresponding answer in JSON output.\n",
        "\n",
        "        Output a JSON object with the keys question and answer\n",
        "        '''\n",
        "\n",
        "        resp = client.chat.completions.create(\n",
        "            model=MODEL,\n",
        "            messages=[{'role': 'user', 'content': prompt}],\n",
        "            response_format={\"type\": \"json_object\"},\n",
        "        )\n",
        "        msg = resp.choices[0].message.content\n",
        "        print('Quiz: ', msg)\n",
        "        quiz = json.loads(msg)\n",
        "        current_state = self.copy_state(state)\n",
        "        current_state.quiz = quiz\n",
        "\n",
        "        return AgentResponse(\n",
        "            agent='🧠 QuizMaster',\n",
        "            message=quiz.get('question'),\n",
        "            current_state=current_state,\n",
        "        )\n",
        "\n",
        "\n",
        "\n",
        "class Learner(Agent):\n",
        "\n",
        "    @override\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        should_be_correct = random.random() < 0.5\n",
        "\n",
        "        prompt = f'''\n",
        "        You are a learner studying {state.subject} at the {state.educational_level} level.\n",
        "\n",
        "        Give a only short {\"correct\" if should_be_correct else \"incorrect\" } answer to the question on {state.topic}:\n",
        "\n",
        "        {state.quiz['question']}\n",
        "        '''\n",
        "\n",
        "        resp = client.chat.completions.create(\n",
        "            model=MODEL,\n",
        "            messages=[{'role': 'user', 'content': prompt}],\n",
        "        )\n",
        "        msg = resp.choices[0].message.content\n",
        "\n",
        "        current_state = self.copy_state(state)\n",
        "        current_state.learner_answer = msg\n",
        "\n",
        "        return AgentResponse(\n",
        "            agent='🧑 Learner',\n",
        "            message=msg,\n",
        "            current_state=current_state,\n",
        "        )\n",
        "\n",
        "\n",
        "class Reviewer(Agent):\n",
        "    @override\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        prompt = f'''\n",
        "        You are an independent reviewer in {state.subject} at the {state.educational_level} level.\n",
        "\n",
        "        The correct answer is: {state.quiz['answer']}\n",
        "        The learner's answer is: {state.learner_answer}\n",
        "        Rate the learner's answer between 0 to 100 with 100 being a perfect answer:\n",
        "\n",
        "        Return only the rating value.\n",
        "        '''\n",
        "\n",
        "        resp = client.chat.completions.create(\n",
        "            model=MODEL,\n",
        "            messages=[{'role': 'user', 'content': prompt}],\n",
        "        )\n",
        "        rating = float(resp.choices[0].message.content)\n",
        "\n",
        "        current_state = self.copy_state(state)\n",
        "\n",
        "        return AgentResponse(\n",
        "            agent='🔍 Reviewer',\n",
        "            message=f'Learner scored {rating}% {'✅' if rating > 50 else '❌'}',\n",
        "            current_state=current_state,\n",
        "        )"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EbuR31Tv9T1E"
      },
      "source": [
        "### Workflow"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "metadata": {
        "id": "NdMy7-fT9V67"
      },
      "outputs": [],
      "source": [
        "class QuizWorkflow(Agent):\n",
        "    def __init__(self):\n",
        "        super().__init__()\n",
        "        print('⚙️ Initializing QuizWorkflow')\n",
        "\n",
        "        self.quiz_master = QuizMaster()\n",
        "        self.learner = Learner()\n",
        "        self.reviewer = Reviewer()\n",
        "\n",
        "\n",
        "    @override\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        current_state = self.copy_state(state)\n",
        "\n",
        "        num_questions = state.num_questions_per_topic\n",
        "        print(f'\\n⚙️ Beginning the quiz workflow for {num_questions} questions\\n')\n",
        "        for i in range(num_questions):\n",
        "\n",
        "            response = self.quiz_master.run(current_state)\n",
        "            add_message(response.agent, response.message)\n",
        "            current_state = response.current_state\n",
        "\n",
        "            response = self.learner.run(current_state)\n",
        "            add_message(response.agent, response.message)\n",
        "            current_state = response.current_state\n",
        "\n",
        "            response = self.reviewer.run(current_state)\n",
        "            add_message(response.agent, response.message)\n",
        "            current_state = response.current_state\n",
        "\n",
        "        print(f'\\n⚙️ Completed workflow')\n",
        "\n",
        "        return AgentResponse(\n",
        "            agent='🔍 QuizWorkflow',\n",
        "            message=f'Done running workflow%',\n",
        "            current_state=current_state,\n",
        "        )\n",
        "\n",
        "\n",
        "class Orchestrator(Agent):\n",
        "    def __init__(self):\n",
        "        super().__init__()\n",
        "        print('⚙️ Initializing Orchestrator')\n",
        "\n",
        "        self.curriculum_designer = CurriculumDesigner()\n",
        "        self.tutor = Tutor()\n",
        "        self.quiz_workflow = QuizWorkflow()\n",
        "\n",
        "\n",
        "    @override\n",
        "    def run(self, state: State) -> AgentResponse:\n",
        "        end_time = datetime.now() + timedelta(minutes=state.run_duration_minutes)\n",
        "        add_message('🤖 Orchestrator', f'Starting autonomous session on \"{state.subject}\" ({state.educational_level})')\n",
        "\n",
        "        response = self.curriculum_designer.run(state=state)\n",
        "        add_message(response.agent, response.message)\n",
        "\n",
        "        state = response.current_state\n",
        "        topics = state.topics\n",
        "\n",
        "        for topic_id, topic in enumerate(topics, start=1):\n",
        "            if datetime.now() >= end_time:\n",
        "                add_message('⏰ Time', 's up! Ending session.')\n",
        "\n",
        "            state.topic = topic\n",
        "            print(f'📌 Topic {topic_id}/{len(topics)}: {topic} ---\\n')\n",
        "\n",
        "            response = self.tutor.run(state)\n",
        "            add_message(response.agent, response.message)\n",
        "\n",
        "            response = self.quiz_workflow.run(state)\n",
        "\n",
        "            print('\\n\\n' + '*' * 80 + '\\n\\n')\n",
        "\n",
        "        print('✅ Session complete! Final mastery:', state.mastery)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zgpfkKjGS-Qb"
      },
      "outputs": [],
      "source": [
        "def initialize_state() -> State:\n",
        "    state = State(\n",
        "        educational_level='Lower Primary',\n",
        "        subject='Chemistry',\n",
        "        mastery={},\n",
        "        topic='',\n",
        "        topics=[],\n",
        "        history=[],\n",
        "        quiz={'question': '', 'answer': ''},\n",
        "        session_start = datetime.now()\n",
        "    )\n",
        "\n",
        "    return state"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LC1dbtiLIsW7"
      },
      "source": [
        "## Run\n",
        "\n",
        "Bare-bones no UI as that is not the focus"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "metadata": {
        "id": "HyaPGM6YUzB6"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "⚙️ Initializing Orchestrator\n",
            "⚙️ Initializing QuizWorkflow\n",
            "\n",
            "[12:06:14] 🤖 Orchestrator: Starting autonomous session on \"Chemistry\" (Lower Primary)\n",
            "\n",
            "\n",
            "[12:06:18] CurriculumDesigner: 5 topics. ['Atoms and Molecules', 'States of Matter', 'Properties of Materials', 'Basic Chemical Reactions', 'Introduction to Elements']\n",
            "\n",
            "📌 Topic 1/5: Atoms and Molecules ---\n",
            "\n",
            "\n",
            "[12:06:21] 📘 Tutor: Atoms are the tiniest pieces that make up everything around us. Think of them like tiny Lego bricks. Each atom has a center called a nucleus with positively charged “protons,” and a few neutrons, and little “electrons” orbit around the nucleus, like tiny balls spinning on invisible strings.  \n",
            "\n",
            "When a few atoms connect, they form a molecule – a little family of atoms that stick together. Imagine two Lego bricks glued together to make a new shape. Water is a molecule made of two hydrogen atoms and one oxygen atom. In the same way, the air we breathe, the food we eat, and even the paper we write on are all made of countless molecules made of atoms.\n",
            "\n",
            "\n",
            "⚙️ Beginning the quiz workflow for 2 questions\n",
            "\n",
            "Quiz:  {\"question\":\"What is a molecule made of?\",\"answer\":\"A molecule is formed when two or more atoms stick together. Each atom is like a tiny building block that makes up everything around us.\"}\n",
            "\n",
            "[12:06:26] 🧠 QuizMaster: What is a molecule made of?\n",
            "\n",
            "\n",
            "[12:06:29] 🧑 Learner: It’s made of little balloons.\n",
            "\n",
            "\n",
            "[12:06:32] 🔍 Reviewer: Learner scored 10.0% ❌\n",
            "\n",
            "Quiz:  {\n",
            "\"question\":\"What is an atom and why do we say it is the smallest part of a chemical substance?\",\n",
            "\"answer\":\"An atom is a tiny particle that makes up all the stuff around us. We say it’s the smallest part of a chemical substance because if you look at any material with a microscope, you can see that it’s made of lots of atoms stuck together and you can’t break an atom down into a smaller piece of that material. Atoms are the building blocks of all the elements we study in chemistry.\"\n",
            "}\n",
            "\n",
            "[12:06:35] 🧠 QuizMaster: What is an atom and why do we say it is the smallest part of a chemical substance?\n",
            "\n",
            "\n",
            "[12:06:38] 🧑 Learner: An **atom** is the tiny, building block that makes up everything in the world.  \n",
            "It contains a nucleus (tiny center) with a few protons and neutrons, and electrons that swirl around it.  \n",
            "\n",
            "We call it the **smallest part of a chemical substance** because even if you cut a substance down to its tiniest pieces, you still end up with whole atoms—none of the atoms can be split into anything smaller that still behaves like a “substance.”\n",
            "\n",
            "\n",
            "[12:06:42] 🔍 Reviewer: Learner scored 100.0% ✅\n",
            "\n",
            "\n",
            "⚙️ Completed workflow\n",
            "\n",
            "\n",
            "********************************************************************************\n",
            "\n",
            "\n",
            "📌 Topic 2/5: States of Matter ---\n",
            "\n",
            "\n",
            "[12:06:45] 📘 Tutor: **Matter can be in three main “states” – solid, liquid, and gas.**  \n",
            "- **Solids** stay in one shape. They have a fixed form and keep their size even when they’re warmed or cooled, like a rock or a piece of chalk.  \n",
            "- **Liquids** can flow and take the shape of the container they’re in, but they still keep the same amount of liquid. Think of water in a cup or milk in a bottle.  \n",
            "- **Gases** spread out and fill any space they’re in. They don’t have a fixed shape or size, like the air in a balloon or the steam that rises from hot soup.  \n",
            "\n",
            "When something moves from one state to another, it changes because of temperature or pressure. For example, heating ice (solid) melts it into water (liquid), and heating that water makes it steam (gas). These are only a few of the ways matter can behave, but solids, liquids, and gases are the most useful categories to learn first!\n",
            "\n",
            "\n",
            "⚙️ Beginning the quiz workflow for 2 questions\n",
            "\n",
            "Quiz:  {\"question\":\"What are the three main states of matter that we can find around us?\",\"answer\":\"The three main states of matter are solids, liquids, and gases.\"}\n",
            "\n",
            "[12:06:48] 🧠 QuizMaster: What are the three main states of matter that we can find around us?\n",
            "\n",
            "\n",
            "[12:06:54] 🧑 Learner: Solid, liquid, and sound.\n",
            "\n",
            "\n",
            "[12:06:58] 🔍 Reviewer: Learner scored 30.0% ❌\n",
            "\n",
            "Quiz:  {\"question\":\"What are the three states of matter that we can see around us?\",\"answer\":\"Solid, liquid, and gas.\"}\n",
            "\n",
            "[12:07:02] 🧠 QuizMaster: What are the three states of matter that we can see around us?\n",
            "\n",
            "\n",
            "[12:07:04] 🧑 Learner: Solid, liquid, and gas.\n",
            "\n",
            "\n",
            "[12:07:06] 🔍 Reviewer: Learner scored 100.0% ✅\n",
            "\n",
            "\n",
            "⚙️ Completed workflow\n",
            "\n",
            "\n",
            "********************************************************************************\n",
            "\n",
            "\n",
            "📌 Topic 3/5: Properties of Materials ---\n",
            "\n",
            "\n",
            "[12:07:10] 📘 Tutor: Materials are things around us—wood, metal, plastic, stone, and even water.  \n",
            "One way to describe them is by their *physical properties*. These are clues you can see, touch, or feel, such as their colour, weight, shape, or whether they’re hard or soft. For example, a metal is usually shiny, heavy, and can be bent into wires, while wood is lighter, can be carved, and feels smooth on a tree bark. Another way is by their *chemical properties*, which show how they react with other things. Think of fire: some materials, like paper, burn easily, while others, like a plastic bottle, can melt or change colour when heated. Knowing both kinds of properties helps us pick the right material for making toys, building houses, or cooking food.\n",
            "\n",
            "\n",
            "⚙️ Beginning the quiz workflow for 2 questions\n",
            "\n",
            "Quiz:  {\"question\":\"What property of a material tells us whether it will float or sink in water?\",\"answer\":\"The property is called density. If a material has a lower density than water, it will float. If it has a higher density than water, it will sink.\"}\n",
            "\n",
            "[12:07:16] 🧠 QuizMaster: What property of a material tells us whether it will float or sink in water?\n",
            "\n",
            "\n",
            "[12:07:18] 🧑 Learner: Density.\n",
            "\n",
            "\n",
            "[12:07:21] 🔍 Reviewer: Learner scored 60.0% ✅\n",
            "\n",
            "Quiz:  {\"question\":\"What is something that can be bent and still stay in the same shape?\",\"answer\":\"A metal like a paperclip can be bent and then returns to its shape.\"}\n",
            "\n",
            "[12:07:24] 🧠 QuizMaster: What is something that can be bent and still stay in the same shape?\n",
            "\n",
            "\n",
            "[12:07:27] 🧑 Learner: Stone.\n",
            "\n",
            "\n",
            "[12:07:30] 🔍 Reviewer: Learner scored 0.0% ❌\n",
            "\n",
            "\n",
            "⚙️ Completed workflow\n",
            "\n",
            "\n",
            "********************************************************************************\n",
            "\n",
            "\n",
            "📌 Topic 4/5: Basic Chemical Reactions ---\n",
            "\n",
            "\n",
            "[12:07:33] 📘 Tutor: Chemical reactions happen when two or more substances mix and change into something new. It’s like a magic trick: you start with old materials (reactants), stir them together, and after a little while you see bubbles, a new colour, or a new smell that didn’t exist before—the new material is called a product.\n",
            "\n",
            "A simple example is mixing baking soda (a base) with vinegar (an acid). The two react and immediately fizz, creating carbon‑dioxide gas that makes bubbles, water, and a new, harmless mixture. That fizzing shows how the reactants have turned into something different!\n",
            "\n",
            "\n",
            "⚙️ Beginning the quiz workflow for 2 questions\n",
            "\n",
            "Quiz:  {\"question\":\"What happens when you mix vinegar and baking soda?\",\"answer\":\"Bubbles form because a chemical reaction releases gas. The fizzing is a gas called carbon dioxide.\"}\n",
            "\n",
            "[12:07:37] 🧠 QuizMaster: What happens when you mix vinegar and baking soda?\n",
            "\n",
            "\n",
            "[12:07:39] 🧑 Learner: When vinegar (acid) meets baking soda (base) they react and fizz. The reaction makes bubbles of carbon‑dioxide gas and forms a gentle, bubbly mixture.\n",
            "\n",
            "\n",
            "[12:07:42] 🔍 Reviewer: Learner scored 95.0% ✅\n",
            "\n",
            "Quiz:  {\"question\":\"If you stir a cup of vinegar with a spoonful of baking soda, what do you see happening?\",\"answer\":\"You’ll see lots of bubbles popping up. The vinegar (acetic acid) and baking soda (sodium bicarbonate) react together to make carbon‑dioxide gas, so the bubbles are the gas coming out of the mixture.\"}\n",
            "\n",
            "[12:07:46] 🧠 QuizMaster: If you stir a cup of vinegar with a spoonful of baking soda, what do you see happening?\n",
            "\n",
            "\n",
            "[12:07:49] 🧑 Learner: You see the liquid fizz and produce bubbles. The vinegar (acetic acid) reacts with baking soda (sodium bicarbonate) to make carbon‑dioxide gas, which escapes as bubbles.\n",
            "\n",
            "\n",
            "[12:07:52] 🔍 Reviewer: Learner scored 100.0% ✅\n",
            "\n",
            "\n",
            "⚙️ Completed workflow\n",
            "\n",
            "\n",
            "********************************************************************************\n",
            "\n",
            "\n",
            "📌 Topic 5/5: Introduction to Elements ---\n",
            "\n",
            "\n",
            "[12:07:57] 📘 Tutor: All the things around us—from the air we breathe to the toys we play with—are made up of tiny, invisible pieces called **elements**. Think of elements like LEGO bricks: just one kind of brick can’t build a whole thing, but when many different bricks are put together, they create cars, trees, and your favourite books. Each element has its own special name and a number that shows how many atoms (the very small units of that element) are inside it.\n",
            "\n",
            "In our everyday life we meet many common elements: **oxygen** helps us breathe, **hydrogen** is a part of water, and **iron** makes the steel in buildings. Even the salt on your cereal is made of tiny parts called elements. By putting these elements together in different ways, scientists can make almost anything we see—just like mixing different LEGO colors builds new shapes!\n",
            "\n",
            "\n",
            "⚙️ Beginning the quiz workflow for 2 questions\n",
            "\n",
            "Quiz:  {\"question\":\"What is an element in chemistry?\",\"answer\":\"An element is a pure substance that is made up of only one type of atom. For example, gold is one element, and iron is another.\"}\n",
            "\n",
            "[12:08:01] 🧠 QuizMaster: What is an element in chemistry?\n",
            "\n",
            "\n",
            "[12:08:04] 🧑 Learner: An element is a kind of cake.\n",
            "\n",
            "\n",
            "[12:08:06] 🔍 Reviewer: Learner scored 0.0% ❌\n",
            "\n",
            "Quiz:  {\"question\":\"What is a chemical element?\",\"answer\":\"A chemical element is a pure substance made of only one type of atom, and it has a special number called an atomic number that tells us how many protons (tiny positive particles) are inside each atom.\"}\n",
            "\n",
            "[12:08:09] 🧠 QuizMaster: What is a chemical element?\n",
            "\n",
            "\n",
            "[12:08:12] 🧑 Learner: A chemical element is just a tiny piece of rock.\n",
            "\n",
            "\n",
            "[12:08:15] 🔍 Reviewer: Learner scored 10.0% ❌\n",
            "\n",
            "\n",
            "⚙️ Completed workflow\n",
            "\n",
            "\n",
            "********************************************************************************\n",
            "\n",
            "\n",
            "✅ Session complete! Final mastery: {}\n"
          ]
        }
      ],
      "source": [
        "# run_autonomous_study_session(duration_minutes=3)\n",
        "\n",
        "orchestrator = Orchestrator()\n",
        "state = initialize_state()\n",
        "orchestrator.run(state)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "metadata": {
        "id": "Q2g0aW9sJOqr"
      },
      "outputs": [
        {
          "data": {
            "application/vnd.jupyter.widget-view+json": {
              "model_id": "a70a9e08c3414afa9931dfe1c624a33a",
              "version_major": 2,
              "version_minor": 0
            },
            "text/plain": [
              "HBox(children=(Text(value='', description='Subject', placeholder='Chemistry'), Dropdown(description='Education…"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "application/vnd.jupyter.widget-view+json": {
              "model_id": "d4c4b63d08ea40b4819701980b86cb44",
              "version_major": 2,
              "version_minor": 0
            },
            "text/plain": [
              "Output(layout=Layout(border_bottom='1px solid #ddd', border_left='1px solid #ddd', border_right='1px solid #dd…"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import ipywidgets as widgets\n",
        "from IPython.display import display, clear_output\n",
        "\n",
        "def add_message(role: str, text: str):\n",
        "    timestamp = datetime.now().strftime('%H:%M:%S')\n",
        "    with output_area:\n",
        "        print(f'\\n[{timestamp}] {role}: {text}\\n')\n",
        "    time.sleep(1)  # optional\n",
        "\n",
        "\n",
        "level = widgets.Dropdown(\n",
        "    options=['Primary', 'Secondary', 'Undergraduate', 'Graduate'],\n",
        "    description='Educational Level',\n",
        ")\n",
        "\n",
        "subject = widgets.Text(description='Subject', placeholder= 'Chemistry')\n",
        "duration = widgets.IntSlider(\n",
        "    value=3,\n",
        "    min=2,\n",
        "    max=30,\n",
        "    step=1,\n",
        "    description='Duration (min):',\n",
        "    style={'description_width': 'initial'}\n",
        ")\n",
        "\n",
        "button = widgets.Button(description='Learn', icon='check')\n",
        "ui = widgets.HBox(children=[subject, level, duration, button])\n",
        "\n",
        "output_area = widgets.Output(\n",
        "    layout={\n",
        "        'width': '100%',\n",
        "        'height': '400px',\n",
        "        'overflow_y': 'auto',\n",
        "        'border': '1px solid #ddd',\n",
        "        'padding': '10px',\n",
        "        'background': '#f9f9f9'\n",
        "    }\n",
        ")\n",
        "\n",
        "\n",
        "def on_learn_click(b):\n",
        "    session_level = level.value\n",
        "    session_subject = subject.value\n",
        "    session_duration = duration.value\n",
        "\n",
        "    with output_area:\n",
        "        if not session_subject:\n",
        "            print('⚠️ Please enter a subject.')\n",
        "            return\n",
        "\n",
        "        print(f'🤖 Starting session on {session_subject} at {session_level} level')\n",
        "\n",
        "        orchestrator = Orchestrator()\n",
        "        state = initialize_state()\n",
        "        state.educational_level = session_level\n",
        "        state.subject = session_subject\n",
        "        state.run_duration_minutes = session_duration\n",
        "\n",
        "        orchestrator.run(state)\n",
        "\n",
        "button.on_click(on_learn_click)\n",
        "\n",
        "display(ui)\n",
        "display(output_area)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": ".venv",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.12.12"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
